use std::collections::HashMap;
use std::hash::Hash;

/// A cacher  
/// This type is used to cache the result of a function call.  
/// It takes a function as input and caches the result of the function call.  
/// # Examples
/// ```
/// use lgrep::utils::Cacher;
/// let mut cacher = Cacher::new(|x| x + 1);
/// assert_eq!(*cacher.value(1), 2);
/// ```
pub struct Cacher<T, V>
{
    func: Box<dyn Fn(T) -> V>,
    value_map: HashMap<T, V>
}

impl<T: Eq + Hash + Clone, V> Cacher<T, V> {
    /// Create a new Cacher instance
    pub fn new(func: impl Fn(T) -> V + 'static) -> Cacher<T, V> {
        Cacher {
            func: Box::new(func),
            value_map: HashMap::new(),
        }
    }

    /// Get the value of the key  
    /// if the key is not exist, call the function to get the value
    /// and cache the value
    pub fn value(&mut self, key: T) -> &V {
        self.value_map.entry(key).or_insert_with_key(|key| (self.func)(key.clone()))
    }

    /// Get the value of the key  
    /// if the key is not exist, return None
    pub fn get(&self, key: T) -> Option<&V> {
        self.value_map.get(&key)
    }
}

#[cfg(test)]
mod tests { 
    use super::*;

    #[test]
    fn call_with_same_value() {
        let c= Cacher::new(|a| a+1);
        let v1 = c.get(1);
        let v2 = c.get(2);
        assert!(v1.is_none());
        assert!(v2.is_none());
    }

    #[test]
    fn call_with_different_values() {
        let mut c = Cacher::new(|a| a+1);

        let v1 = c.value(1);
        assert_eq!(*v1, 2);
        let v2 = c.value(2);
        assert_eq!(*v2, 3);
        let v1 = c.get(1);
        let v2 = c.get(2);
        assert!(v1.is_some());
        assert!(v2.is_some());
        assert_ne!(v1.unwrap(), v2.unwrap());

        let mut c = Cacher::new(|a| format!("cache func: {}", a) );

        let v1 = c.value(&1);
        assert_eq!(*v1, "cache func: 1");
        let v2 = c.value(&2);
        assert_eq!(*v2, "cache func: 2");
    }
}
